#2的tSNE降维版
import time
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from numpy import nonzero, array
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.manifold import TSNE


# 1. 加载数据
data = pd.read_csv('StudentPerformanceFactors.csv')
print(f"原始数据量: {len(data)} 条")

# 删除包含空值的行
data = data.dropna()
print(f"去除空值后的数据量: {len(data)} 条")
# 二元分类编码
binary_cols = {
    'Extracurricular_Activities': {'No': 0, 'Yes': 1},
    'Internet_Access': {'No': 0, 'Yes': 1},
    'Learning_Disabilities': {'No': 0, 'Yes': 1}
}
#遍历 multi_cols 字典，对每一列进行数值映射
for col, mapping in binary_cols.items():
    data[col] = data[col].map(mapping)

# 多分类编码
multi_cols = {
    'Parental_Involvement': {'Low': 0, 'Medium': 1, 'High': 2},
    'Access_to_Resources': {'Low': 0, 'Medium': 1, 'High': 2},
    'Motivation_Level': {'Low': 0, 'Medium': 1, 'High': 2},
    'Family_Income': {'Low': 0, 'Medium': 1, 'High': 2},
    'Teacher_Quality': {'Low': 0, 'Medium': 1, 'High': 2},
    'School_Type': {'Public': 0, 'Private': 1},
    'Peer_Influence': {'Negative': 0, 'Neutral': 1, 'Positive': 2},
    'Parental_Education_Level': {'High School': 0, 'College': 1, 'Postgraduate': 2},
    'Distance_from_Home': {'Near': 0, 'Moderate': 1, 'Far': 2},
    'Gender': {'Male': 0, 'Female': 1}
}

for col, mapping in multi_cols.items():
    data[col] = data[col].map(mapping)

# 3. 选择特征列（可手动选择或保留部分列）
#全部标签如下
# cat_cols = ['Hours_Studied','Attendance','Parental_Involvement', 'Access_to_Resources', 'Extracurricular_Activities','Sleep_Hours','Previous_Scores',
#             'Motivation_Level', 'Internet_Access', 'Tutoring_Sessions','Family_Income', 'Teacher_Quality',
#             'School_Type', 'Peer_Influence', 'Physical_Activity','Learning_Disabilities',
#             'Parental_Education_Level', 'Distance_from_Home', 'Gender']
drop_cols = ['Parental_Involvement', 'Extracurricular_Activities',
             'Attendance', 'Internet_Access', 'Family_Income',
             'Distance_from_Home', 'Physical_Activity',
             'Access_to_Resources', 'School_Type', 'Peer_Influence',
             'Gender', 'Sleep_Hours', 'Tutoring_Sessions']
features = data.drop(columns=drop_cols)
print(f"用于聚类的特征维度: {features.shape[1]}")
print("使用的特征列:", features.columns.tolist())
attributes = features.shape[1]
#---------------------------------------------以上为数据预处理----------------------------------------------------#

#---------------------------------------------以下为聚类算法------------------------------------------------------#
# K-Means 聚类及评估
def initialize_centroids(data, k):
    # 从数据集中随机选择k个点作为初始质心
    centers = data[np.random.choice(data.shape[0], k, replace=False)]
    return centers


def get_clusters(data, centroids):
    # 计算数据点与质心之间的距离，并将数据点分配给最近的质心
    distances = np.linalg.norm(data[:, np.newaxis] - centroids, axis=2)
    cluster_labels = np.argmin(distances, axis=1)
    return cluster_labels


def update_centroids(data, cluster_labels, k):
    # 计算每个簇的新质心，即簇内数据点的均值
    new_centroids = np.array([data[cluster_labels == i].mean(axis=0) for i in range(k)])
    return new_centroids


def k_means(data, k, T, epsilon):
    start = time.time()  # 开始时间，计时
    # 初始化质心
    centroids = initialize_centroids(data, k)
    t = 0
    while t <= T:
        # 分配簇
        cluster_labels = get_clusters(data, centroids)

        # 更新质心
        new_centroids = update_centroids(data, cluster_labels, k)

        # 检查收敛条件
        if np.linalg.norm(new_centroids - centroids) < epsilon:
            break
        centroids = new_centroids
        print("第", t, "次迭代")
        t += 1
    #print("用时：{0}".format(time.time() - start))
    print(f"聚类完成，迭代次数: {t+1}, 用时: {time.time()-start:.2f}秒")
    return cluster_labels, centroids


def evaluate_clustering(data, labels):
    """评估聚类结果（无监督指标）"""
    silhouette = silhouette_score(data, labels)
    print(f"轮廓系数(Silhouette Score): {silhouette:.3f}")
    return silhouette

def visualize_clusters(data, centers, labels, k, perplexity=30, random_state=42):
    """
    使用t-SNE降维可视化聚类结果

    参数:
        data: 原始高维数据 (n_samples, n_features)
        centers: 聚类中心 (n_clusters, n_features)
        labels: 聚类标签 (n_samples,)
        k: 聚类数量
        perplexity: t-SNE的困惑度参数
        random_state: 随机种子
    """
    # 合并数据和中心点用于统一t-SNE变换
    combined_data = np.vstack([data, centers])

    # t-SNE降维
    tsne = TSNE(n_components=2,
                perplexity=perplexity,
                random_state=random_state,
                init='pca',
                learning_rate='auto')

    print("正在执行t-SNE降维...")
    start_time = time.time()
    transformed_data = tsne.fit_transform(combined_data)
    print(f"t-SNE降维完成，用时: {time.time()-start_time:.2f}秒")

    # 分离数据和中心点
    data_2d = transformed_data[:-k]  # 前n_samples个点是原始数据
    centers_2d = transformed_data[-k:]  # 最后k个点是中心点

    # 设置颜色和样式
    colors = plt.colormaps['tab20'].resampled(k)
    plt.figure(figsize=(12, 8))

    # 绘制聚类结果
    for i in range(k):
        cluster_points = data_2d[labels == i]
        plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
                   color=colors(i),
                   label=f'Cluster {i} (n={len(cluster_points)})',
                   alpha=0.6,
                   edgecolors='w',
                   s=50)

    # 绘制质心
    plt.scatter(centers_2d[:, 0], centers_2d[:, 1],
               marker='*',
               s=400,
               c='red',
               edgecolors='k',
               linewidths=1,
               label='Centroids',
               zorder=10)

    # 添加图例和标签
    plt.title(f'K-means Clustering Results (t-SNE projection)\nPerplexity={perplexity}', pad=20)
    plt.xlabel('t-SNE Dimension 1')
    plt.ylabel('t-SNE Dimension 2')

    # 改进图例显示
    leg = plt.legend(loc='upper right', frameon=True)
    leg.get_frame().set_facecolor('white')
    leg.get_frame().set_alpha(0.8)

    # 添加网格和调整布局
    plt.grid(True, linestyle='--', alpha=0.3)
    plt.tight_layout()

    # 显示KL散度（t-SNE的优化目标）
    print(f"t-SNE最终KL散度: {tsne.kl_divergence_:.3f}")

    plt.show()

if __name__ == "__main__":
    # 转换为numpy数组
    features_array = np.array(features)

    silhouette_scores = []  # 初始化一个空列表用于记录silhouette分数
    for k in range(2, min(10, len(features_array))):  # 循环计算 silhouette score（轮廓分数）
        cluster_labels, _ = k_means(features_array, k, T=100, epsilon=1e-5)
        if len(set(cluster_labels)) > 1:
            score = silhouette_score(features_array, cluster_labels)
            silhouette_scores.append(score)
        else:
            silhouette_scores.append(-1)

    best_k = np.argmax(silhouette_scores) + 2  # 取得分数最大的k值，加2是因为K从2开始试的
    print(f"最优K值: {best_k}, 对应的Silhouette得分: {silhouette_scores[best_k - 2]:.4f}")

    # 聚类参数
    k = best_k
    print(f"选取k = {k}\n")
    max_iter = 100  # 最大迭代次数
    epsilon = 1e-5  # 收敛阈值

    # 执行K-means聚类
    cluster_labels, centroids = k_means(features_array, k, max_iter, epsilon)

    # 评估聚类结果
    evaluate_clustering(features_array, cluster_labels)

    # 可视化聚类结果
    visualize_clusters(features_array, centroids, cluster_labels, k)